/*
 * main.c: Kernel main (entry) function
 *
 * Author: 	Nora Tarano <ntarano@andrew.cmu.edu>
 *          Yuan Meng <yuanm@andrew.cmu.edu>
 *          Yipeng Yun <yipengy@andrew.cmu.edu>
 * Date: Nov 17, 2011
 *
 * Author: Mike Kasick <mkasick@andrew.cmu.edu>
 * Date:   Sat, 13 Oct 2007 23:56:04 -0400
 */

#include <exports.h>
#include <inline.h>

#include <bits/errno.h>
#include <bits/fileno.h>
#include <bits/swi.h>

#include <arm/psr.h>
#include <arm/exception.h>
#include <arm/interrupt.h>
#include <arm/timer.h>
#include <arm/reg.h>

#define SWI_VECTOR_ADDR 0x00000008
#define IRQ_VECTOR_ADDR 0x00000018
#define PC_OFFSET       8

#define LDR_PC_BASE  0xe51ff000 /* ldr pc, [pc, #imm12] */
#define LDR_U_FLAG   0x00800000
#define LDR_IMM_MASK 0x00000fff
#define LDR_PC_NEXT  0xe51ff004 /* ldr pc, [pc, #-4] */

#define ICMR_TIMER_MASK	0x04000000
#define ICLR_TIMER_LVL  0x0

#define FREQUENCY_MS	3250

// Special characters
#define EOT 0x4
#define BACKSPACE 0x8
#define DELETE 127
#define NEWLINE 0xa
#define RETURN 0xd

#define DEBUG 0

// kernel_asm
int enter_user_mode(int, char**);
void exit_kernel(int status);

// swi_handler
void swi_handler(void);
unsigned int swi_dispatch(unsigned swi_num, unsigned *regs);
void invalid_syscall(void);

// syscalls
unsigned read_syscall(int fd, char *buf, unsigned count);
unsigned write_syscall(int fd, const char *buf, unsigned count);
void exit_syscall(int status);
unsigned long time_syscall(void);
void sleep_syscall(unsigned long millis);

// interrupt_handler
void irq_handler(void);
void c_irq_handler(void);
inline void setup_interrupt_controller(void);
inline void setup_os_timer(void);
void setup_irq_stack(void);
void unmask_irq();
void mask_irq();

// global vars
uint32_t global_data;
uint32_t icmr_init;
uint32_t iclr_init;
uint32_t oier_init;
volatile unsigned long system_time;

/* "Wires in" the SWI handler and switches to user mode. */
int kmain(int argc, char *argv[], uint32_t table) {
	unsigned long swi_vector = *(unsigned long *)SWI_VECTOR_ADDR;
	unsigned long irq_vector = *(unsigned long *)IRQ_VECTOR_ADDR;
	unsigned long *old_swi_handler, old_swi_inst0, old_swi_inst1;
	unsigned long *old_irq_handler, old_irq_inst0, old_irq_inst1;
	long swi_offset;
	long irq_offset;
	int status;
	
	app_startup(); /* bss is valid after this point */
 	global_data = table;

	// Check if the SWI and IRQ vectors contain "ldr pc, [pc, #imm12]"
	if ((swi_vector & ~(LDR_U_FLAG | LDR_IMM_MASK)) != LDR_PC_BASE) {
		printf("Kernel panic: unrecognized SWI vector: %08lx\n", swi_vector);
		return 0x0badc0de;
	}
	if ((irq_vector & ~(LDR_U_FLAG | LDR_IMM_MASK)) != LDR_PC_BASE) {
		printf("Kernel panic: unrecognized IRQ vector: %08lx\n", irq_vector);
		return 0x0badc0de;
	}

	// Determine the literal offsets for SWI and IRQ 
	if (swi_vector & LDR_U_FLAG)
		swi_offset = swi_vector & LDR_IMM_MASK;
	else
		swi_offset = -(swi_vector & LDR_IMM_MASK);
	if (irq_vector & LDR_U_FLAG)
		irq_offset = irq_vector & LDR_IMM_MASK;
	else
		irq_offset = -(irq_vector & LDR_IMM_MASK);

	// Find the location of U-Boot's SWI & IRQ handlers
	old_swi_handler = 
		*(unsigned long **)(SWI_VECTOR_ADDR + PC_OFFSET + swi_offset);
	old_irq_handler = 
		*(unsigned long **)(IRQ_VECTOR_ADDR + PC_OFFSET + irq_offset);

	// Save the first two instructions of each handler for later restoration
	old_swi_inst0 = *(old_swi_handler  );
	old_swi_inst1 = *(old_swi_handler+1);
	old_irq_inst0 = *(old_irq_handler  );
	old_irq_inst1 = *(old_irq_handler+1);

	// Redirect U-Boot's SWI and IRQ handlers to ours
	*(old_swi_handler  ) = LDR_PC_NEXT;
	*(old_swi_handler+1) = (unsigned long) swi_handler;
	*(old_irq_handler  ) = LDR_PC_NEXT;
	*(old_irq_handler+1) = (unsigned long) irq_handler;
	
	// Setup IRQ stack
	if (DEBUG) printf("Setting up irq stack\n");
	setup_irq_stack();
	
	if (DEBUG) printf("Setting up interrupt controller and os timer\n");
	mask_irq();
 	setup_interrupt_controller();
	setup_os_timer();

	if (DEBUG) printf("Entering user mode\n------------------\n");
	status = enter_user_mode(argc, argv);

	// Restore the old handlers
	*(old_swi_handler  ) = old_swi_inst0;
	*(old_swi_handler+1) = old_swi_inst1;
	*(old_irq_handler  ) = old_irq_inst0;
	*(old_irq_handler+1) = old_irq_inst1;
	
	// restore interrupt controller
	reg_write(INT_ICMR_ADDR, icmr_init);
	reg_write(INT_ICLR_ADDR, iclr_init);

	return status;
}

// Exits the kernel, never returns to user mode.
void exit_syscall(int status) {
	exit_kernel(status);
}

// An invalid syscall causes the kernel to exit.
void invalid_syscall(void) {
	puts("Kernel panic: invalid syscall!\n");
	exit_kernel(0x0badc0de);
}

// Read syscall: reads count bytes from stdin and echoes them in buf
unsigned read_syscall(int fd, char *buf, unsigned count) {
	unsigned int bufIndex;
	char tempChar;

	// check for argument errors
	if (fd != STDIN_FILENO)
		return -EBADF;
	if ((unsigned int)buf + count > 0xa2ffffff || (unsigned int)buf < 0xa0000000)
		return -EFAULT;
		
	// reading procedure
	for (bufIndex = 0; bufIndex < count; ++bufIndex) {
		// read char from stdin
		tempChar = getc();
		// interpret special characters
		if (tempChar == EOT)
			return bufIndex;
		if (tempChar == BACKSPACE || tempChar == DELETE) {
			bufIndex--;
			bufIndex--;
			puts("\b \b");
			continue;
		}
		// store and display read character
		buf[bufIndex] = tempChar;
		putc(buf[bufIndex]);
		// interpret newline / carriage return characters
		if (buf[bufIndex] == NEWLINE || buf[bufIndex] == RETURN) {
			putc('\n');
			return bufIndex + 1;
		}
	}
	
	putc('\n');
	return bufIndex;
}

// Write syscall: writes count bytes from buf to stdout
unsigned write_syscall(int fd, const char *buf, unsigned count) {
	unsigned int bufIndex;
	
	// check for argument errors
	if (fd != STDOUT_FILENO)
		return -EBADF;
	if (!(((unsigned int)buf >= 0xa000000) && ((unsigned int)buf + count <= 0xa3ffffff))
		&& !((unsigned int)buf + count <= 0x00ffffff))
		return -EFAULT;
	
	// output buf to stdout
	for (bufIndex = 0; bufIndex < count; bufIndex++)
		putc(buf[bufIndex]);
	
	putc('\n');
	return count;
}

// sleep_syscall: t in ms
void sleep_syscall(unsigned long t) {
	if (DEBUG) printf("Sleeping...\n");

	// take care of observed drift by subtracting 4 from system_time
	unsigned long alarm = system_time - 4 + t;

	while (alarm != system_time);
	
	if (DEBUG) printf("Awake!\n");
}

// time_syscall: system_time in ms
unsigned long time_syscall() {
	return system_time;
}

// swi_dispatch:  makes appropriate system calls based on swi_num
unsigned int swi_dispatch(unsigned swi_num, unsigned *regs) {
	unsigned int ret = 0;
	
	if (DEBUG) printf("dispatching swi...\n");
	
	if (swi_num != EXIT_SWI)
		unmask_irq();
	
	// make appropriate syscalls
	switch (swi_num) {
		case EXIT_SWI:
			if (DEBUG) printf("EXIT_SWI: %x, %x\n", swi_num, (unsigned int)regs);
			exit_syscall(regs[0]);
			break;
			
		case READ_SWI:
			if (DEBUG) printf("READ_SWI: %x, %x\n", swi_num, (unsigned int)regs);
			ret =  read_syscall(regs[0], (char *)(regs[1]), regs[2]);
			break;
			
		case WRITE_SWI:
			if (DEBUG) printf("WRITE_SWI: %x, %x\n", swi_num, (unsigned int)regs);
			ret = write_syscall(regs[0], (char *)(regs[1]), regs[2]);
			break;
		
		case SLEEP_SWI:
			if (DEBUG) printf("SLEEP_SWI: %d\n", (unsigned int) regs[0]);
			sleep_syscall(regs[0]);
			break;
			
		case TIME_SWI:
			if (DEBUG) printf("TIME_SWI\n");
			ret = time_syscall();
			break;
			
		default: invalid_syscall();
	}
	
	if (swi_num != EXIT_SWI) {
		mask_irq();
	}
	
	// store the return value in r0
	c->r0 = ret;
	
	return ret;
}

// Setup Interrupt Controller Registers appropriately
inline void setup_interrupt_controller() {
	icmr_init = reg_read(INT_ICMR_ADDR);
	iclr_init = reg_read(INT_ICLR_ADDR);
	reg_write(INT_ICMR_ADDR, ICMR_TIMER_MASK);
	reg_write(INT_ICLR_ADDR, ICLR_TIMER_LVL);
}

// Reset all registers and enable timer 0
inline void setup_os_timer() {	
	oier_init = reg_read(OSTMR_OIER_ADDR);
	system_time = 0;
	reg_write(OSTMR_OSMR_ADDR(0), 1*FREQUENCY_MS);
	reg_write(OSTMR_OIER_ADDR, OSTMR_OIER_E0);
	reg_write(OSTMR_OSSR_ADDR, 0x0);
	reg_write(OSTMR_OSCR_ADDR, 0x0);
}

void c_irq_handler(void) {
	//unsigned long oscr = reg_read(OSTMR_OSCR_ADDR);
	system_time += 1;
	
	// acknowledge IRQ
	reg_write(OSTMR_OSSR_ADDR, OSTMR_OSSR_M0);
	
	// reset all registers
	reg_write(OSTMR_OSMR_ADDR(0), 1*FREQUENCY_MS);
	reg_write(OSTMR_OSCR_ADDR, 0x0);
}
